Stroke - Datenbeschreibung und Analyse¶

Einleitung¶

Nach Angaben der Weltgesundheitsorganisation (WHO) ist der Schlaganfall die zweithäufigste Todesursache weltweit und für etwa 11% der Gesamttodesfälle verantwortlich.

Dieser Datensatz wird verwendet, um vorherzusagen, ob ein Patient wahrscheinlich einen Schlaganfall erleiden wird, basierend auf Eingabeparametern wie Geschlecht, Alter, verschiedene Krankheiten und Raucherstatus (insgesamt 11 klinische Merkmale).

Es soll ein Frühwarnsystem erstellt werden, das anhand der im Datensatz erfassten klinischen Merkmale das Risiko eines Schlaganfalls prognostizieren kann. Das System soll Ärzte in Ihrer Arbeit unterstützen.

Beschreibung der Daten¶

Quelle¶

Download Stroke Prediction Dataset

Use only for educational purposes

Beschreibung der Spalten¶

  1. id: unique identifier
  2. gender: "Male", "Female" or "Other"
  3. age: age of the patient
  4. hypertension: 0 if the patient doesn't have hypertension, 1 if the patient has hypertension
  5. heart_disease: 0 if the patient doesn't have any heart diseases, 1 if the patient has a heart disease
  6. ever_married: "No" or "Yes"
  7. work_type: "children", "Govt_jov", "Never_worked", "Private" or "Self-employed"
  8. Residence_type: "Rural" or "Urban"
  9. avg_glucose_level: average glucose level in blood
  10. bmi: body mass index
  11. smoking_status: "formerly smoked", "never smoked", "smokes" or "Unknown"*
  12. stroke: 1 if the patient had a stroke or 0 if not *Note: "Unknown" in smoking_status means that the information is unavailable for this patient

Daten importieren¶

In [1]:
import pandas as pd
import plotly.express as px 
import plotly as pt 
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("healthcare-dataset-stroke-data.csv")
df.head()
Out[1]:
id gender age hypertension heart_disease ever_married work_type Residence_type avg_glucose_level bmi smoking_status stroke
0 9046 Male 67.0 0 1 Yes Private Urban 228.69 36.6 formerly smoked 1
1 51676 Female 61.0 0 0 Yes Self-employed Rural 202.21 NaN never smoked 1
2 31112 Male 80.0 0 1 Yes Private Rural 105.92 32.5 never smoked 1
3 60182 Female 49.0 0 0 Yes Private Urban 171.23 34.4 smokes 1
4 1665 Female 79.0 1 0 Yes Self-employed Rural 174.12 24.0 never smoked 1

Datenkennzahlen¶

Datenvolumen und Datentypen¶

In [2]:
df.shape
Out[2]:
(5110, 12)
In [3]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5110 entries, 0 to 5109
Data columns (total 12 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   id                 5110 non-null   int64  
 1   gender             5110 non-null   object 
 2   age                5110 non-null   float64
 3   hypertension       5110 non-null   int64  
 4   heart_disease      5110 non-null   int64  
 5   ever_married       5110 non-null   object 
 6   work_type          5110 non-null   object 
 7   Residence_type     5110 non-null   object 
 8   avg_glucose_level  5110 non-null   float64
 9   bmi                4909 non-null   float64
 10  smoking_status     5110 non-null   object 
 11  stroke             5110 non-null   int64  
dtypes: float64(3), int64(4), object(5)
memory usage: 479.2+ KB

Die Spalten mit dem Datentyp object werden im Data Preprocessing passend für die eingesetzten Machine Learning Verfahren in float umgewandelt.

(siehe Stroke.py, function init_data(...))

Unwichtige Spalten entfernen¶

Die Spalte id liefert keine wertvolle Information und wird daher entfernt.

In [4]:
df.drop(columns=['id'], inplace=True)

Statistische Kennzahlen¶

In [5]:
df.describe()
Out[5]:
age hypertension heart_disease avg_glucose_level bmi stroke
count 5110.000000 5110.000000 5110.000000 5110.000000 4909.000000 5110.000000
mean 43.226614 0.097456 0.054012 106.147677 28.893237 0.048728
std 22.612647 0.296607 0.226063 45.283560 7.854067 0.215320
min 0.080000 0.000000 0.000000 55.120000 10.300000 0.000000
25% 25.000000 0.000000 0.000000 77.245000 23.500000 0.000000
50% 45.000000 0.000000 0.000000 91.885000 28.100000 0.000000
75% 61.000000 0.000000 0.000000 114.090000 33.100000 0.000000
max 82.000000 1.000000 1.000000 271.740000 97.600000 1.000000
  • der Datensatz enthält keine Minima oder Maxima, die auf Platzhalterwerte oder künstlich aufgefüllte Werte hinweisen.
  • die Altersangabe '0.08' scheint valide, da es einem Monat entspricht.
  • die Wertebereiche von 'bmi' und 'avg_glucose_level' erscheinen uns sehr groß, aber noch plausibel

Fehlende Werte¶

In [6]:
df.isnull().sum()
Out[6]:
gender                 0
age                    0
hypertension           0
heart_disease          0
ever_married           0
work_type              0
Residence_type         0
avg_glucose_level      0
bmi                  201
smoking_status         0
stroke                 0
dtype: int64

Im Datensatz fehlen in der Spalte bmi Werte, die im Data Preprocessing durch einen KNNImputer ergänzt werden

(siehe Stroke.py, function prepare_data(...))

Duplikate¶

In [7]:
df.duplicated().sum()
Out[7]:
0

Der Datensatz enthält (unter Ausschluss der Spalte id) keine Duplikate.

Wertebereiche der Spalten mit kategorischen Daten¶

In [8]:
df['gender'].unique()
Out[8]:
array(['Male', 'Female', 'Other'], dtype=object)
In [9]:
df['smoking_status'].unique()
Out[9]:
array(['formerly smoked', 'never smoked', 'smokes', 'Unknown'],
      dtype=object)
In [10]:
df['Residence_type'].unique()
Out[10]:
array(['Urban', 'Rural'], dtype=object)
In [11]:
df['work_type'].unique()
Out[11]:
array(['Private', 'Self-employed', 'Govt_job', 'children', 'Never_worked'],
      dtype=object)
In [12]:
df['ever_married'].unique()
Out[12]:
array(['Yes', 'No'], dtype=object)

Im Data Preprocessing werden die Spalten mit kategorischen Daten transformiert.

(siehe Stroke.py, function init_data(...))

Einheiten¶

Die Einheiten der verschiedenen Merkmale widersprechen sich nicht.

Korrelationen zwischen den Merkmalen und der Zielspalte¶

Wir untersuchen, wie die Merkmale mit der Zielspalte korrelieren (Patienten mit/ohne Schlaganfall), um einen ersten Eindruck von den Daten zu erhalten.

Anteil von Schlaganfällen¶

In [13]:
ratio = df[df['stroke'] == 1].shape[0] / df.shape[0]
print(f'{ratio * 100:.2f}%')
4.87%
In [14]:
fig = px.pie(df,
             names='stroke',
             title=f'<b>Anteil an Patienten mit Schlaganfall</b>',
             category_orders={"stroke": [1,0]},
             color_discrete_sequence=['rgb(200, 0, 0)', 'lightgray'], 
             width=350,
             height=350
            )
fig.show()

Der Datensatz ist unbalanciert. Nur knapp 5 % sind Daten von Patienten mit Schlaganfall. Da Schlaganfälle vorhergesagt werden sollen, liegt auf dieser Datenimbalance im Folgenden ein besonderes Augenmerk.

Durchschnitt der Merkmale¶

In [15]:
stroke = df[df['stroke'] == 1].describe().T
no_stroke = df[df['stroke'] == 0].describe().T

colors = ['#808080']

fig,ax = plt.subplots(nrows = 1,ncols = 2,figsize = (5,5))

plt.subplot(1,2,1)
sns.heatmap(stroke[['mean']],annot = True,cmap = colors,linewidths = 0.4,linecolor = 'black',cbar = False,fmt = '.2f')
plt.title('Schlaganfall')

plt.subplot(1,2,2)
sns.heatmap(no_stroke[['mean']],annot = True,cmap = colors,linewidths = 0.4,linecolor = 'black',cbar = False,fmt = '.2f')
plt.title('kein Schlaganfall')

fig.tight_layout(pad = 0)
  • Es zeigt sich, dass die Merkmale mit der Zielspalte korrelieren, was eine gute Voraussetzung dafür ist, dass die im Folgenden angewendeten Verfahren des maschinellen Lernens Erfolg haben können.
  • die Korrelation zwischen Schlaganfall und bmi ist vergleichsweise schwach
  • die Korrelation zwischen Schlaganfall und Blutzuckerspiegel avg_glucose_level ist vergleichsweise stark
  • Patienten, die bereits unter einer Herzkrankheit leiden, haben öfter einen Schlaganfall
  • im Wesentlichen entspricht diese Auswertung den Erwartungen des gesunden Menschenverstandes
  • (überraschende Erkenntnis: Je älter man ist, desto größer ist die Chance auf einen Schlaganfall)

Korrelationsmatrix der Merkmale¶

In [16]:
plt.subplots(figsize=(12,5))
sns.heatmap(df.corr(numeric_only=True),annot=True,cmap='RdPu')   # 'df.corr()' falls der Parameter 'numeric_only' Probleme macht
plt.title('Korrelation zwischen den Merkmalen')
plt.xticks(rotation=0)
Out[16]:
(array([0.5, 1.5, 2.5, 3.5, 4.5, 5.5]),
 [Text(0.5, 0, 'age'),
  Text(1.5, 0, 'hypertension'),
  Text(2.5, 0, 'heart_disease'),
  Text(3.5, 0, 'avg_glucose_level'),
  Text(4.5, 0, 'bmi'),
  Text(5.5, 0, 'stroke')])
  • diese Auswertung zeigt zusätzlich zur vorigen Auswertung die Korrelationen aller Merkmale untereinander.
  • die starke Abhängigkeit des Alters mit alle anderen Merkmale ist dabei nicht überraschend.
  • keine der Korrelationen ist so hervorstechend, dass sie besondere Verarbeitungsschritte nahe legt.

Zusammenfassung und Abschlussbemerkungen¶

  • der Datensatz ist von hoher Qualität und erfordert wenig Aufbereitungaufwand
  • problematisch ist die Unbalanciertheit der Daten bezüglich der Zielspalte stroke
  • das Data Preprocessing ist hier nur mit Querverweisen angedeutet und ist im Abschlussbericht detaillierter beschrieben